/**
 * 
 */
package systole.domain.signals;

import java.math.BigDecimal;
import java.util.Vector;
import systole.math.MathUtils;

/**
 * Represents a sign of dots arranged temporarily.
 *
 *  @author juan
 */
public class Segment extends Vector<BigDecimal> {

    /**
     *
     */
    private static final long serialVersionUID = -5125247557196959977L;
    private static BigDecimal MAX_ERROR = new BigDecimal(2.5);

    /**
     * Empty Constructor
     */
    public Segment() {
        super();
    }

    /**
     * @param capacity
     */
    public Segment(int capacity) {
        super(capacity);
    }

    /**
     * @param arrayData
     */
    public Segment(double[] arrayData) {
        super(arrayData.length);

        for (int i = 0; i < arrayData.length; i++) {
            this.add(new BigDecimal(arrayData[i]));
        }
    }

    /**
     * Return the average points of the vector.
     *
     * @return Points average.
     */
    public BigDecimal avg() {
        BigDecimal prom = BigDecimal.ZERO;
        if (!this.isEmpty()) {
            for (int i = 0; i < this.size(); i++) {
                prom = prom.add(this.elementAt(i));
            }
            prom = prom.divide(new BigDecimal(this.size()), MathUtils.CONTEXT);
        }
        return prom;
    }

    /**
     *
     * Return a subsegment of vector.
     *
     *@param initOfSegment First point of subsegment.
     *@param endOfSegment Last point of subsegment.
     *
     * @return Subsegment
     */
    public Segment subSegment(int initOfSegment, int endOfSegment) {
        Segment segment = new Segment();

        while ((initOfSegment < this.size()) & (initOfSegment <= endOfSegment)) {
            segment.add(this.elementAt(initOfSegment));
            initOfSegment++;
        }
        return segment;
    }

    /**
     *Return the minimum value of vector.
     *
     * @return The minimum value of segment.
     */
    public BigDecimal min() {
        BigDecimal min = new BigDecimal(100.0);
        int i = 0;
        while ((i < this.size())) {
            if (this.elementAt(i).compareTo(min) == -1) {
                min = this.elementAt(i);
            }
            i++;
        }
        return min;
    }

    /**
     * @return the index of minimum value
     */
    public int getPosOfMin() {
        BigDecimal min = new BigDecimal(100.0);
        int posOfMin = 0;
        int i = 0;
        while ((i < this.size())) {
            if (this.elementAt(i).compareTo(min) == -1) {
                min = this.elementAt(i);
                posOfMin = i;
            }
            i++;
        }
        return posOfMin;
    }

    /**
     * Return the maximum value of vector.
     *
     *
     * @return The maximum value of segment.
     */
    public BigDecimal max() {
        BigDecimal max = BigDecimal.ZERO;
        int i = 0;
        while ((i < this.size())) {
            if (this.elementAt(i).compareTo(max) == 1) {
                max = this.elementAt(i);
            }
            i++;
        }
        return max;
    }

    /**
     * @return the index of maximum value
     */
    public int getPosOfMax() {
        BigDecimal max = BigDecimal.ZERO;
        int posOfMax = 0;
        int i = 0;
        while ((i < this.size())) {
            if (this.elementAt(i).compareTo(max) == 1) {
                max = this.elementAt(i);
                posOfMax = i;
            }
            i++;
        }
        return posOfMax;
    }

    /**
     * @param initialPos
     * @param finalPos
     * @return index of minimum value
     */
    public int getPosOfMin(int initialPos, int finalPos) {
        return this.subSegment(initialPos, finalPos).getPosOfMin();
    }

    /**
     * @param initialPos
     * @param finalPos
     * @return index of maximum value
     */
    public int getPosOfMax(int initialPos, int finalPos) {
        return this.subSegment(initialPos, finalPos).getPosOfMax();
    }

    /**
     * Return the minimum of a subsegment of vector.
     *
     * @param initialPos  Initial position of subsegment.
     * @param finalPos  Final position of subsegment.
     * @return The minimum value of subsegment.
     */
    public BigDecimal min(int initialPos, int finalPos) {
        return this.subSegment(initialPos, finalPos).min();
    }

    /**
     *
     * Return the maximum of a subsegment of vector.
     *
     * @param initialPos  Initial position of subsegment.
     * @param finalPos  Final position of subsegment.
     * @return The maximum value of subsegment.
     */
    public BigDecimal max(int initialPos, int finalPos) {
        return this.subSegment(initialPos, finalPos).max();
    }

    /**
     * Multiply a constant to each tip of vector
     *
     * @param factor Value for multiply.
     */
    public void multiplyVectorBy(BigDecimal factor) {
        for (int i = 0; i < this.size(); i++) {
            this.set(i, this.get(i).multiply(factor));
        }
    }

    /**
     * Subtracts a constant to each tip of vector
     *
     * @param value Value to subtract.
     */
    public void subtractValueToVector(BigDecimal value) {
        for (int i = 0; i < this.size(); i++) {
            this.set(i, this.get(i).subtract(value));
        }
    }

    /**
     *
     * Adds a constant to each tip of vector
     *
     * @param value Value to add.
     */
    public void addValuteToVector(BigDecimal value) {
        for (int i = 0; i < this.size(); i++) {
            this.set(i, this.get(i).add(value));
        }
    }

    /**
     *
     * For each element multiplied by minus one and adds the average
     * doubled
     *
     * @return Return the segment inverted.
     */
    public Segment invert() {
        BigDecimal prom = this.avg();
        Segment inverted = new Segment();
        int i = 0;
        while (i < this.size()) {
            inverted.add(this.elementAt(i).multiply(new BigDecimal(-1.0)).add(prom.multiply(new BigDecimal(2.0))));
            i++;
        }
        return inverted;
    }

    /**
     *
     * Normalize the vector between maximum and minimum value.
     *
     * @return A segment normalized.
     */
    public Segment normalize() {
        BigDecimal min = this.min();
        BigDecimal max = this.max();
        if (!max.equals(min)) {
            BigDecimal gain = new BigDecimal(100.0).divide(max.subtract(min), MathUtils.CONTEXT);
            this.subtractValueToVector(min);
            this.multiplyVectorBy(gain);
        }
        return this;
    }

    /**
     * @return array of double
     */
    public double[] toDoubleArray() {
        //Probar - Tal vez hayan problemas de clonado
        double[] elements = new double[this.size()];

        for (int i = 0; i < elements.length; i++) {
            elements[i] = this.elementAt(i).doubleValue();
        }
        return elements;
    }

    /**
     * @param value
     * @param initOfSegment
     * @param endOfSegment
     * @return nears position to value
     */
    public Integer looksPositionOfNearestValueOnSubSegment(BigDecimal value, int initOfSegment, int endOfSegment) {
        return this.subSegment(initOfSegment, endOfSegment).looksPositionOfNearestValue(value);
    }

    /**
     * @param value
     * @return nears position to value
     */
    public Integer looksPositionOfNearestValue(BigDecimal value) {
        Integer pos = 0;
        BigDecimal lastValue = BigDecimal.ZERO;
        int i = 0;
        boolean next = true;
        while ((i < this.size()) & (next)) {
            if (value.subtract(this.get(i)).abs().compareTo(value.subtract(lastValue).abs()) == -1) {
                lastValue = this.get(i);
                next = (value.compareTo(lastValue) != 0);
                pos = i;
            }
            i++;
        }
        return pos;
    }

    /**
     * @param value
     * @param initOfSegment
     * @param endOfSegment
     * @return nears position to value
     */
    public Integer looksFirstPositionOfNearestValueOnSubSegment(BigDecimal value, int initOfSegment, int endOfSegment) {
        return this.subSegment(initOfSegment, endOfSegment).looksFirstPositionOfNearestValue(value);
    }

    /**
     * @param value
     * @return nears position to value
     */
    public Integer looksFirstPositionOfNearestValue(BigDecimal value) {
        Integer pos = 0;
        int i = 0;
        if (value.compareTo(this.get(i)) == -1) {
            while ((i < this.size()) && (value.compareTo(this.get(i)) == -1)) {
                pos = i;
                i++;
            }
            return pos;
        } else {
            if (value.compareTo(this.get(i)) == 1) {
                while ((i < this.size()) && (value.compareTo(this.get(i)) == 1)) {
                    pos = i;
                    i++;
                }
                return pos;
            } else {
                return pos;
            }
        }
    }

    /**
     * @param value
     * @param initOfSegment
     * @param endOfSegment
     * @return nears position to value
     */
    public Integer looksFirstPositionOfNearestValueOnSubSegmentDowTo(BigDecimal value, int initOfSegment, int endOfSegment) {
        return this.subSegment(initOfSegment, endOfSegment).looksFirstPositionOfNearestValueDownTo(value);
    }

    /**
     * @param value
     * @return nears position to value
     */
    public Integer looksFirstPositionOfNearestValueDownTo(BigDecimal value) {
        Integer pos = this.size() - 1;
        int i = this.size() - 1;
        BigDecimal multiplo = new BigDecimal(-1);
        //verifico si es mayo o menor
        if (value.compareTo(this.get(i)) == -1) {
            // mientras sea menor y no se termine la lista sigo buscando
            while ((i >= 0) && (value.compareTo(this.get(i)) == -1)) {
                pos = i;
                i--;
            }
            // si encontre uno menor antes de terminar la lista
            if (i >= 0) {
                // asigno el actual o el anterior dependiendo cual este mas cerca
                pos = (((value.subtract(this.get(pos))).multiply(multiplo).compareTo(((value.subtract(this.get(pos - 1))))) == 1) ? pos + 1 : pos);
            }
            return pos;
        } else {
            if (value.compareTo(this.get(i)) == 1) {
                while ((i >= 0) && (value.compareTo(this.get(i)) == 1)) {
                    pos = i;
                    i--;
                }
                if (i >= 0) {
                    // asigno el actual o el anterior dependiendo cual este mas cerca
                    pos = (((value.subtract(this.get(pos)).compareTo(((value.subtract(this.get(pos - 1)).multiply(multiplo)))))==1) ? pos + 1 : pos);
                }
                return pos;

            } else {
                return pos;
            }
        }
    }

    /**
     * @param initPos
     * @return the index of maximum value from initPos
     */
    public int searchFirstMax(int initPos) {
        BigDecimal max = BigDecimal.ZERO;
        int posOfMax = 0;
        int i = initPos;
        boolean maxFound = false;
        while ((i < this.size())) {
            if (this.elementAt(i).compareTo(max) == 1) {
                max = this.elementAt(i);
                posOfMax = i;
                maxFound = true;
            } else {
                if ((maxFound) && (this.elementAt(i).subtract(max).compareTo(Segment.MAX_ERROR) == 1)) {
                    return posOfMax;
                }
            }
            i++;
        }
        return posOfMax;
    }

    /**
     * @return index of first maximum
     */
    public int searchFirstMax() {
        return this.searchFirstMax(0);
    }

    /**
     * 
     * @param initPos
     * @return the first minimum from initPos
     */
    public int searchFirstMin(int initPos) {
        BigDecimal min = new BigDecimal(1000.0);
        int posOfMin = 0;
        int i = initPos;
        boolean minFound = false;
        while ((i < this.size())) {
            if (this.elementAt(i).compareTo(min) == -1) {
                min = this.elementAt(i);
                posOfMin = i;
                minFound = true;
            } else {
                if ((minFound) && (min.subtract(this.elementAt(i)).compareTo(Segment.MAX_ERROR)) == -1) {
                    return posOfMin;
                }
            }
            i++;
        }
        return posOfMin;
    }

    /**
     *
     * @param initPos
     * @return the first minimum from initPos down to
     */
    public int searchFirstMinDownTo(int initPos) {
        BigDecimal min = new BigDecimal(1000.0);
        int posOfMin = 0;
        int i = initPos;
        boolean minFound = false;
        while ((i < 0) && (i < this.size())) {
            if (this.elementAt(i).compareTo(min) == -1) {
                min = this.elementAt(i);
                posOfMin = i;
                minFound = true;
            } else {
                if ((minFound) && ((min.subtract(this.elementAt(i))).compareTo(Segment.MAX_ERROR)) == 1) {
                    return posOfMin;
                }
            }
            i--;
        }
        return posOfMin;
    }

    /**
     *
     * @return the first minimum 
     */
    public int searchFirstMin() {
        return this.searchFirstMin(0);
    }

    /**
     *
     * @return the first minimum down to 
     */
    public int searchFirstMinDownTo() {
        return this.searchFirstMinDownTo(0);
    }

    @Override
    public String toString() {
        String result = "";

        for (int i = 0; i < this.size(); i++) {
            result = result + this.elementAt(i) + " ";
        }
        return result;

    }

    /**
     * @param initPos
     * @param endPos
     * @return the segment without trend 
     */
    public Segment removeTrend(int initPos, int endPos) {

        Segment segmentWithOutTrend = new Segment();

        int x0 = initPos;
        int x1 = endPos;

        BigDecimal y0 = this.elementAt(initPos);
        BigDecimal y1 = this.elementAt(endPos);

        BigDecimal m = (y1.subtract(y0)).divide(new BigDecimal(x1 - x0), MathUtils.CONTEXT);

        int i = initPos;
        while (i <= endPos) {
            segmentWithOutTrend.add(this.elementAt(i).subtract(m.multiply(new BigDecimal(i))));
            i++;
        }

        return segmentWithOutTrend;
    }

    /**
     * @return the segment without trend
     */
    public Segment removeTrend() {
        return this.removeTrend(0, this.size() - 1);
    }

    /**
     * @param initPos
     * @param endPos
     * @return if the slope is negative
     */
    public boolean isSlopeNegative(int initPos, int endPos) {

        int x0 = initPos;
        int x1 = endPos;

        BigDecimal y0 = this.elementAt(initPos);
        BigDecimal y1 = this.elementAt(endPos);

        BigDecimal m = (y1.subtract(y0)).divide(new BigDecimal(x1 - x0), MathUtils.CONTEXT);

        return m.compareTo(BigDecimal.ZERO) > 0;
    }

    /* (non-Javadoc)
     * @see java.util.Vector#equals(java.lang.Object)
     */
    @Override
    public synchronized boolean equals(Object o) {
        return super.equals(o);
    }

    /* (non-Javadoc)
     * @see java.util.Vector#hashCode()
     */
    @Override
    public synchronized int hashCode() {
        return super.hashCode();
    }
}
